import { app, BrowserWindow, dialog } from "electron";
import fs from "fs";
import path from "path";
import TOML from "smol-toml";
import BeanFactory from "../core/BeanFactory";
import GlobalConstant from "../core/GlobalConstant";
import Logger from "../core/Logger";
import ProxyRepository from "../repository/ProxyRepository";
import ServerRepository from "../repository/ServerRepository";
import PathUtils from "../utils/PathUtils";
import BaseService from "./BaseService";

class ServerService extends BaseService<OpenSourceFrpcDesktopServer> {
  private readonly _serverDao: ServerRepository;
  private readonly _proxyDao: ProxyRepository;
  // private readonly _systemService: SystemService;
  private readonly _serverId: string = "1";

  constructor(
    serverDao: ServerRepository,
    proxyDao: ProxyRepository
    // systemService: SystemService
  ) {
    super();
    this._serverDao = serverDao;
    this._proxyDao = proxyDao;
    // this._systemService = systemService;
  }

  async saveServerConfig(
    frpcServer: OpenSourceFrpcDesktopServer
  ): Promise<OpenSourceFrpcDesktopServer> {
    frpcServer._id = this._serverId;
    const newConfig = await this._serverDao.updateById(
      this._serverId,
      frpcServer
    );
    try {
      app.setLoginItemSettings({
        openAtLogin: newConfig.system.launchAtStartup || false, //win
        openAsHidden: newConfig.system.launchAtStartup || false //macOs
      });
    } catch (error) {
      Logger.error("ServerService.saveServerConfig", error);
    }
    Logger.setLevel(newConfig.log.level);
    return newConfig;
  }

  async getServerConfig(): Promise<OpenSourceFrpcDesktopServer> {
    return await this._serverDao.findById(this._serverId);
  }

  hasServerConfig(): Promise<boolean> {
    return new Promise((resolve, reject) => {
      this._serverDao
        .exists(this._serverId)
        .then(async r => {
          if (r) {
            const config = await this.getServerConfig();
            resolve(!!config && !!config.serverAddr);
          } else {
            resolve(false);
          }
        })
        .catch(err => reject(err));
    });
  }

  private isRagePort(proxy: FrpcProxy) {
    return (
      ["tcp", "udp"].indexOf(proxy.type) >= 0 &&
      (String(proxy.localPort).indexOf("-") !== -1 ||
        String(proxy.localPort).indexOf(",") !== -1)
    );
  }

  private isVisitors(proxy: FrpcProxy) {
    return (
      ["stcp", "sudp", "xtcp"].indexOf(proxy.type) >= 0 &&
      proxy.visitorsModel === "visitors"
    );
  }

  private isEnableProxy(proxy: FrpcProxy) {
    return proxy.status === 1;
  }

  private isHttps2http(proxy: FrpcProxy) {
    return proxy.https2http;
  }

  async genTomlConfig(outputPath: string) {
    if (!outputPath) {
      return;
    }
    const server = await this.getServerConfig();
    const proxies = await this._proxyDao.findAll();

    const enabledRangePortProxies = proxies
      .filter(f => this.isEnableProxy(f))
      .filter(f => !this.isVisitors(f))
      .filter(f => this.isRagePort(f))
      .map(proxy => {
        return `
{{- range $_, $v := parseNumberRangePair "${proxy.localPort}" "${proxy.remotePort}" }}
[[proxies]]

type = "${proxy.type}"
name = "${proxy.name}-{{ $v.First }}"
localIP = "${proxy.localIP}"
localPort = {{ $v.First }}
remotePort = {{ $v.Second }}
{{- end }}
`;
      });

    const enabledProxies = proxies
      .filter(f => this.isEnableProxy(f))
      .filter(f => !this.isVisitors(f))
      .filter(f => !this.isRagePort(f))
      .map(proxy => {
        if (proxy.type === "tcp" || proxy.type === "udp") {
          const localPort = parseInt(proxy.localPort);
          const remotePort = parseInt(proxy.remotePort);
          return {
            name: proxy.name,
            type: proxy.type,
            localIP: proxy.localIP,
            localPort: localPort,
            remotePort: remotePort,
            transport: proxy.transport
          };
        } else if (proxy.type === "http" || proxy.type === "https") {
          const locations = proxy.locations.filter(l => l !== "");
          if (this.isHttps2http(proxy) && proxy.type === "https") {
            return {
              name: proxy.name,
              type: proxy.type,
              customDomains: proxy.customDomains,
              subdomain: proxy.subdomain,
              transport: proxy.transport,
              ...(locations.length > 0 ? { locations } : {}),
              ...(proxy.https2http
                ? {
                    plugin: {
                      type: "https2http",
                      localAddr: `${proxy.localIP}:${proxy.localPort}`,
                      crtPath: proxy.https2httpCaFile,
                      keyPath: proxy.https2httpKeyFile
                    }
                  }
                : {})
            };
          } else {
            return {
              name: proxy.name,
              type: proxy.type,
              localIP: proxy.localIP,
              localPort: parseInt(proxy.localPort),
              customDomains: proxy.customDomains,
              transport: proxy.transport,
              subdomain: proxy.subdomain,
              ...(locations.length > 0 ? { locations } : {}),
              ...(proxy.basicAuth
                ? { httpUser: proxy.httpUser, httpPassword: proxy.httpPassword }
                : {})
            };
          }
        } else if (
          proxy.type === "stcp" ||
          proxy.type === "xtcp" ||
          proxy.type === "sudp"
        ) {
          return {
            name: proxy.name,
            type: proxy.type,
            transport: proxy.transport,
            localIP: proxy.localIP,
            localPort: parseInt(proxy.localPort),
            secretKey: proxy.secretKey
          };
        }
      });

    const enableVisitors = proxies
      .filter(f => this.isEnableProxy(f))
      .filter(f => this.isVisitors(f))
      .map(proxy => {
        if (proxy.type === "xtcp") {
          return {
            name: proxy.name,
            type: proxy.type,
            // serverUser: proxy.serverUser,
            serverName: proxy.serverName,
            secretKey: proxy.secretKey,
            bindAddr: proxy.bindAddr,
            bindPort: proxy.bindPort,
            keepTunnelOpen: proxy.keepTunnelOpen,
            fallbackTo: proxy.fallbackTo,
            fallbackTimeoutMs: proxy.fallbackTimeoutMs
          };
        } else {
          return {
            name: proxy.name,
            type: proxy.type,
            serverName: proxy.serverName,
            secretKey: proxy.secretKey,
            bindAddr: proxy.bindAddr,
            bindPort: proxy.bindPort
          };
        }
      });

    const { frpcVersion, _id, system, multiuser, ...commonConfig } = server;
    const frpcConfig = { ...commonConfig };
    frpcConfig.log.to = PathUtils.getFrpcLogFilePath();
    frpcConfig.loginFailExit = GlobalConstant.FRPC_LOGIN_FAIL_EXIT;
    frpcConfig.webServer.addr = GlobalConstant.LOCAL_IP;

    if (frpcConfig.auth.method === "none") {
      frpcConfig.auth = null;
    }

    let toml = TOML.stringify({
      ...frpcConfig,
      ...(enabledProxies.length > 0 ? { proxies: enabledProxies } : {}),
      ...(enableVisitors.length > 0 ? { visitors: enableVisitors } : {})
    });

    enabledRangePortProxies.forEach(f => {
      toml += `
${f}`;
    });

    fs.writeFileSync(outputPath, toml, { flag: "w" });
  }

  async importTomlConfig() {
    const win: BrowserWindow = BeanFactory.getBean("win");
    const result = await dialog.showOpenDialog(win, {
      properties: ["openFile"],
      filters: [{ name: "Frpc Toml ConfigFile", extensions: ["toml"] }]
    });
    if (result.canceled) {
      return {
        canceled: true,
        path: ""
      };
    } else {
      const filePath = result.filePaths[0];
      const fileExtension = path.extname(filePath);
      if (fileExtension === GlobalConstant.TOML_EXT) {
        const tomlData = fs.readFileSync(filePath, "utf-8");
        const sourceConfig = TOML.parse(tomlData);
        // 默认配置
        const config: OpenSourceFrpcDesktopServer = {
          _id: "",
          multiuser: false,
          frpcVersion: null,
          loginFailExit: false,
          udpPacketSize: 1500,
          serverAddr: "",
          serverPort: 7000,
          auth: {
            method: "",
            token: ""
          },
          log: {
            to: "",
            level: "info",
            maxDays: 3,
            disablePrintColor: false
          },
          transport: {
            dialServerTimeout: 10,
            dialServerKeepalive: 7200,
            poolCount: 0,
            tcpMux: true,
            tcpMuxKeepaliveInterval: 30,
            protocol: "tcp",
            connectServerLocalIP: "",
            proxyURL: "",
            tls: {
              enable: true,
              certFile: "",
              keyFile: "",
              trustedCaFile: "",
              serverName: "",
              disableCustomTLSFirstByte: true
            },
            heartbeatInterval: 30,
            heartbeatTimeout: 90
          },
          metadatas: {
            token: ""
          },
          webServer: {
            addr: "127.0.0.1",
            port: 57400,
            user: "",
            password: "",
            pprofEnable: false
          },
          system: {
            launchAtStartup: false,
            silentStartup: false,
            autoConnectOnStartup: false,
            language: "en-US"
          },
          user: ""
        };
        // 判断 toml 中是否有这些配置项，有则覆盖默认的
        if (sourceConfig.loginFailExit !== undefined) {
          config.loginFailExit = sourceConfig.loginFailExit as boolean;
        }
        if (sourceConfig.udpPacketSize !== undefined) {
          config.udpPacketSize = sourceConfig.udpPacketSize as number;
        }
        if (sourceConfig.serverAddr !== undefined) {
          config.serverAddr = sourceConfig.serverAddr as string;
        }
        if (sourceConfig.serverPort !== undefined) {
          config.serverPort = sourceConfig.serverPort as number;
        }
        if (sourceConfig.user !== undefined) {
          config.user = sourceConfig.user as string;
        }

        // auth 配置
        if (sourceConfig.auth) {
          if ((sourceConfig.auth as AuthConfig).method !== undefined) {
            config.auth.method = (sourceConfig.auth as AuthConfig)
              .method as string;
          }
          if ((sourceConfig.auth as AuthConfig).token !== undefined) {
            config.auth.token = (sourceConfig.auth as AuthConfig)
              .token as string;
          }
        }

        // log 配置
        if (sourceConfig.log as LogConfig) {
          if ((sourceConfig.log as LogConfig).to !== undefined) {
            config.log.to = (sourceConfig.log as LogConfig).to as string;
          }
          if ((sourceConfig.log as LogConfig).level !== undefined) {
            config.log.level = (sourceConfig.log as LogConfig).level as string;
          }
          if ((sourceConfig.log as LogConfig).maxDays !== undefined) {
            config.log.maxDays = (sourceConfig.log as LogConfig)
              .maxDays as number;
          }
          if ((sourceConfig.log as LogConfig).disablePrintColor !== undefined) {
            config.log.disablePrintColor = (sourceConfig.log as LogConfig)
              .disablePrintColor as boolean;
          }
        }

        // transport 配置
        if (sourceConfig.transport as TransportConfig) {
          if (
            (sourceConfig.transport as TransportConfig).dialServerTimeout !==
            undefined
          ) {
            config.transport.dialServerTimeout = (
              sourceConfig.transport as TransportConfig
            ).dialServerTimeout as number;
          }
          if (
            (sourceConfig.transport as TransportConfig).dialServerKeepalive !==
            undefined
          ) {
            config.transport.dialServerKeepalive = (
              sourceConfig.transport as TransportConfig
            ).dialServerKeepalive as number;
          }
          if (
            (sourceConfig.transport as TransportConfig).poolCount !== undefined
          ) {
            config.transport.poolCount = (
              sourceConfig.transport as TransportConfig
            ).poolCount as number;
          }
          if (
            (sourceConfig.transport as TransportConfig).tcpMux !== undefined
          ) {
            config.transport.tcpMux = (
              sourceConfig.transport as TransportConfig
            ).tcpMux as boolean;
          }
          if (
            (sourceConfig.transport as TransportConfig)
              .tcpMuxKeepaliveInterval !== undefined
          ) {
            config.transport.tcpMuxKeepaliveInterval = (
              sourceConfig.transport as TransportConfig
            ).tcpMuxKeepaliveInterval as number;
          }
          if (
            (sourceConfig.transport as TransportConfig).protocol !== undefined
          ) {
            config.transport.protocol = (
              sourceConfig.transport as TransportConfig
            ).protocol as string;
          }
          if (
            (sourceConfig.transport as TransportConfig).connectServerLocalIP !==
            undefined
          ) {
            config.transport.connectServerLocalIP = (
              sourceConfig.transport as TransportConfig
            ).connectServerLocalIP as string;
          }
          if (
            (sourceConfig.transport as TransportConfig).proxyURL !== undefined
          ) {
            config.transport.proxyURL = (
              sourceConfig.transport as TransportConfig
            ).proxyURL as string;
          }
          if (
            (sourceConfig.transport as TransportConfig).heartbeatInterval !==
            undefined
          ) {
            config.transport.heartbeatInterval = (
              sourceConfig.transport as TransportConfig
            ).heartbeatInterval as number;
          }
          if (
            (sourceConfig.transport as TransportConfig).heartbeatTimeout !==
            undefined
          ) {
            config.transport.heartbeatTimeout = (
              sourceConfig.transport as TransportConfig
            ).heartbeatTimeout as number;
          }

          // transport.tls 配置
          if (sourceConfig.transport as TransportTlsConfig) {
            if (
              (sourceConfig.transport as TransportTlsConfig).enable !==
              undefined
            ) {
              config.transport.tls.enable = (
                sourceConfig.transport as TransportTlsConfig
              ).enable as boolean;
            }
            if (
              (sourceConfig.transport as TransportTlsConfig).certFile !==
              undefined
            ) {
              config.transport.tls.certFile = (
                sourceConfig.transport as TransportTlsConfig
              ).certFile as string;
            }
            if (
              (sourceConfig.transport as TransportTlsConfig).keyFile !==
              undefined
            ) {
              config.transport.tls.keyFile = (
                sourceConfig.transport as TransportTlsConfig
              ).keyFile as string;
            }
            if (
              (sourceConfig.transport as TransportTlsConfig).trustedCaFile !==
              undefined
            ) {
              config.transport.tls.trustedCaFile = (
                sourceConfig.transport as TransportTlsConfig
              ).trustedCaFile as string;
            }
            if (
              (sourceConfig.transport as TransportTlsConfig).serverName !==
              undefined
            ) {
              config.transport.tls.serverName = (
                sourceConfig.transport as TransportTlsConfig
              ).serverName as string;
            }
            if (
              (sourceConfig.transport as TransportTlsConfig)
                .disableCustomTLSFirstByte !== undefined
            ) {
              config.transport.tls.disableCustomTLSFirstByte = (
                sourceConfig.transport as TransportTlsConfig
              ).disableCustomTLSFirstByte as boolean;
            }
          }
        }

        // metadatas 配置
        if (sourceConfig.metadatas as Record<string, any>) {
          if (
            (sourceConfig.metadatas as Record<string, any>).token !== undefined
          ) {
            config.metadatas.token = (
              sourceConfig.metadatas as Record<string, any>
            ).token as string;
          }
        }

        // webServer 配置
        if (sourceConfig.webServer as WebServerConfig) {
          if ((sourceConfig.webServer as WebServerConfig).addr !== undefined) {
            config.webServer.addr = (sourceConfig.webServer as WebServerConfig)
              .addr as string;
          }
          if ((sourceConfig.webServer as WebServerConfig).port !== undefined) {
            config.webServer.port = (sourceConfig.webServer as WebServerConfig)
              .port as number;
          }
          if ((sourceConfig.webServer as WebServerConfig).user !== undefined) {
            config.webServer.user = (sourceConfig.webServer as WebServerConfig)
              .user as string;
          }
          if (
            (sourceConfig.webServer as WebServerConfig).password !== undefined
          ) {
            config.webServer.password = (
              sourceConfig.webServer as WebServerConfig
            ).password as string;
          }
          if (
            (sourceConfig.webServer as WebServerConfig).pprofEnable !==
            undefined
          ) {
            config.webServer.pprofEnable = (
              sourceConfig.webServer as WebServerConfig
            ).pprofEnable as boolean;
          }
        }

        await this.saveServerConfig(config);

        if (sourceConfig && sourceConfig.proxies) {
          const proxies = (sourceConfig.proxies as any[]).map((proxy: any) => {
            const proxy2: FrpcProxy = {
              _id: "",
              hostHeaderRewrite: "",
              locations: [""],
              name: "",
              type: "http",
              localIP: "",
              localPort: "8080",
              remotePort: "8080",
              customDomains: [""],
              visitorsModel: "visitors",
              serverName: "",
              secretKey: "",
              bindAddr: "",
              bindPort: null,
              subdomain: "",
              basicAuth: false,
              httpUser: "",
              httpPassword: "",
              fallbackTo: "",
              fallbackTimeoutMs: 500,
              https2http: false,
              https2httpCaFile: "",
              https2httpKeyFile: "",
              keepTunnelOpen: false,
              status: 1,
              transport: {
                useEncryption: false,
                useCompression: false,
                proxyProtocolVersion: ""
              }
            };

            if (proxy.name !== undefined) {
              proxy2.name = proxy.name as string;
            }
            if (proxy.type !== undefined) {
              proxy2.type = proxy.type as string;
            }
            if (proxy.localIP !== undefined) {
              proxy2.localIP = proxy.localIP as string;
            }
            if (proxy.localPort !== undefined) {
              proxy2.localPort = proxy.localPort.toString();
            }
            if (proxy.remotePort !== undefined) {
              proxy2.remotePort = proxy.remotePort.toString();
            }
            if (proxy.customDomains !== undefined) {
              proxy2.customDomains = proxy.customDomains as string[];
            }
            if (proxy.subdomain !== undefined) {
              proxy2.subdomain = proxy.subdomain as string;
            }
            if (proxy.locations !== undefined) {
              proxy2.locations = proxy.locations as string[];
            }
            if (proxy.hostHeaderRewrite !== undefined) {
              proxy2.hostHeaderRewrite = proxy.hostHeaderRewrite as string;
            }
            if (proxy.httpUser !== undefined) {
              proxy2.httpUser = proxy.httpUser as string;
            }
            if (proxy.httpPassword !== undefined) {
              proxy2.httpPassword = proxy.httpPassword as string;
            }
            if (proxy.serverName !== undefined) {
              proxy2.serverName = proxy.serverName as string;
            }
            if (proxy.secretKey !== undefined) {
              proxy2.secretKey = proxy.secretKey as string;
            }
            if (proxy.bindAddr !== undefined) {
              proxy2.bindAddr = proxy.bindAddr as string;
            }
            if (proxy.bindPort !== undefined) {
              proxy2.bindPort = proxy.bindPort as number;
            }
            if (proxy.fallbackTo !== undefined) {
              proxy2.fallbackTo = proxy.fallbackTo as string;
            }
            if (proxy.fallbackTimeoutMs !== undefined) {
              proxy2.fallbackTimeoutMs = proxy.fallbackTimeoutMs as number;
            }
            if (proxy.keepTunnelOpen !== undefined) {
              proxy2.keepTunnelOpen = proxy.keepTunnelOpen as boolean;
            }

            // 处理 transport 配置
            if (proxy.transport) {
              if (proxy.transport.useEncryption !== undefined) {
                proxy2.transport.useEncryption = proxy.transport
                  .useEncryption as boolean;
              }
              if (proxy.transport.useCompression !== undefined) {
                proxy2.transport.useCompression = proxy.transport
                  .useCompression as boolean;
              }
              if (proxy.transport.proxyProtocolVersion !== undefined) {
                proxy2.transport.proxyProtocolVersion = proxy.transport
                  .proxyProtocolVersion as string;
              }
            }

            return proxy2;
          });
          await this._proxyDao.insertMany(proxies);
        }

        if (sourceConfig && sourceConfig.visitors) {
          const visitors = (sourceConfig.visitors as any[]).map(
            (visitor: any) => {
              const visitor2: FrpcProxy = {
                _id: "",
                hostHeaderRewrite: "",
                locations: [""],
                name: "",
                type: "http",
                localIP: "",
                localPort: "8080",
                remotePort: "8080",
                customDomains: [""],
                visitorsModel: "visitors",
                serverName: "",
                secretKey: "",
                bindAddr: "",
                bindPort: null,
                subdomain: "",
                basicAuth: false,
                httpUser: "",
                httpPassword: "",
                fallbackTo: "",
                fallbackTimeoutMs: 500,
                https2http: false,
                https2httpCaFile: "",
                https2httpKeyFile: "",
                keepTunnelOpen: false,
                status: 1,
                transport: {
                  useEncryption: false,
                  useCompression: false,
                  proxyProtocolVersion: ""
                }
              };

              if (visitor.name !== undefined) {
                visitor2.name = visitor.name as string;
              }
              if (visitor.type !== undefined) {
                visitor2.type = visitor.type as string;
              }
              if (visitor.serverName !== undefined) {
                visitor2.serverName = visitor.serverName as string;
              }
              if (visitor.secretKey !== undefined) {
                visitor2.secretKey = visitor.secretKey as string;
              }
              if (visitor.bindAddr !== undefined) {
                visitor2.bindAddr = visitor.bindAddr as string;
              }
              if (visitor.bindPort !== undefined) {
                visitor2.bindPort = visitor.bindPort as number;
              }

              if (visitor.transport) {
                if (visitor.transport.useEncryption !== undefined) {
                  visitor2.transport.useEncryption = visitor.transport
                    .useEncryption as boolean;
                }
                if (visitor.transport.useCompression !== undefined) {
                  visitor2.transport.useCompression = visitor.transport
                    .useCompression as boolean;
                }
                if (visitor.transport.proxyProtocolVersion !== undefined) {
                  visitor2.transport.proxyProtocolVersion = visitor.transport
                    .proxyProtocolVersion as string;
                }
              }

              return visitor2;
            }
          );
          await this._proxyDao.insertMany(visitors);
        }
      } else {
        throw new Error(`导入失败，暂不支持 ${fileExtension} 格式文件`);
      }
      return {
        canceled: false,
        path: filePath
      };
    }
  }

  async isSilentStart() {
    const serverConfig = await this.getServerConfig();
    if (serverConfig) {
      return serverConfig.system.silentStartup;
    } else {
      return false;
    }
  }

  async isAutoConnectOnStartup() {
    const serverConfig = await this.getServerConfig();
    if (serverConfig) {
      return serverConfig.system.autoConnectOnStartup;
    } else {
      return false;
    }
  }

  async getLoggerLevel() {
    const serverConfig = await this.getServerConfig();
    if (serverConfig) {
      return serverConfig.log.level;
    } else {
      return "info";
    }
  }

  async getLanguage() {
    const serverConfig = await this.getServerConfig();
    let language = undefined;
    if (serverConfig) {
      language = serverConfig.system.language;
    }
    if (!language) {
      language = GlobalConstant.DEFAULT_LANGUAGE;
    }
    return language;
  }

  async saveLanguage(language: string) {
    let serverConfig = await this.getServerConfig();
    if (!serverConfig) {
      serverConfig = {
        _id: "",
        multiuser: false,
        frpcVersion: null,
        loginFailExit: false,
        udpPacketSize: 1500,
        serverAddr: "",
        serverPort: 7000,
        auth: {
          method: "",
          token: ""
        },
        log: {
          to: "",
          level: "info",
          maxDays: 3,
          disablePrintColor: false
        },
        transport: {
          dialServerTimeout: 10,
          dialServerKeepalive: 7200,
          poolCount: 0,
          tcpMux: true,
          tcpMuxKeepaliveInterval: 30,
          protocol: "tcp",
          connectServerLocalIP: "",
          proxyURL: "",
          tls: {
            enable: true,
            certFile: "",
            keyFile: "",
            trustedCaFile: "",
            serverName: "",
            disableCustomTLSFirstByte: true
          },
          heartbeatInterval: 30,
          heartbeatTimeout: 90
        },
        metadatas: {
          token: ""
        },
        webServer: {
          addr: "127.0.0.1",
          port: 57400,
          user: "",
          password: "",
          pprofEnable: false
        },
        system: {
          launchAtStartup: false,
          silentStartup: false,
          autoConnectOnStartup: false,
          language: language
        },
        user: ""
      };
    } else {
      serverConfig.system.language = language;
    }
    await this.saveServerConfig(serverConfig);
  }
}

export default ServerService;
